/* 
Copyright Paul James Mutton, 2001-2004, http://www.jibble.org/

This file is part of SimpleFTP.

This software is dual-licensed, allowing you to choose between the GNU
General Public License (GPL) and the www.jibble.org Commercial License.
Since the GPL may be too restrictive for use in a proprietary application,
a commercial license is also provided. Full license information can be
found at http://www.jibble.org/licenses/

$Author: pjm2 $
$Id: SimpleFTP.java,v 1.2 2004/05/29 19:27:37 pjm2 Exp $

*/

package simpleftp.client.control;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.OptionalDataException;
import java.io.OutputStreamWriter;
import java.io.StreamCorruptedException;
import java.net.Socket;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.tree.DefaultMutableTreeNode;

import com.google.gson.Gson;

import simpleftp.ftp.FTPDescriber;
import simpleftp.ftp.FileData;
import simpleftp.ftp.FileTreeNode;


/**
 * SimpleFTP is a simple package that implements a Java FTP client.
 * With SimpleFTP, you can connect to an FTP server and upload multiple files.
 *  <p>
 * Copyright Paul Mutton,
 *           <a href="http://www.jibble.org/">http://www.jibble.org/</a>
 * 
 */
public class SimpleFTP extends FTPDescriber {
	
    private DefaultMutableTreeNode directoryTree = null;
    
    private String currentServerDirectory = null;
    
    private int 	port;
    private String 	ip;
    
    SimpleFTP() {
    	
    }
    
    SimpleFTP(String directory) {
    	this.currentServerDirectory = directory;
    }
    
    /**
     * Connects to the default port of an FTP server and logs in as
     * anonymous/anonymous.
     */
    public void connect(String host) throws IOException {
        connect(host, 21);
    }
    
    
    /**
     * Connects to an FTP server and logs in as anonymous/anonymous.
     */
    public void connect(String host, int port) throws IOException {
        connect(host, port, "anonymous", "anonymous");
    }
    
    
    /**
     * Connects to an FTP server and logs in with the supplied username
     * and password.
     */
    public void connect(String host, int port, String user, String pass) throws IOException {
        if (socket != null) {
            throw new IOException("SimpleFTP is already connected. Disconnect first.");
        }
        
        socket = new Socket(host, port);
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        getDirectory = new ObjectInputStream(socket.getInputStream());
        
        String response = readLine();
        if (!response.startsWith("220 ")) {
            throw new IOException("SimpleFTP received an unknown response when connecting to the FTP server: " + response);
        }
        
        sendLine("USER " + user);
        
        response = readLine();
        
        if (!response.startsWith("331 ")) {
        	System.out.println("2");
            throw new IOException("SimpleFTP received an unknown response after sending the user: " + response);
        }
        sendLine("PASS " + pass);
        
        response = readLine();
        if (!response.startsWith("230 ")) {
            throw new IOException("SimpleFTP was unable to log in with the supplied password: " + response);
        }
        // Now logged in.
        
        if(currentServerDirectory != null)
        	changeStartDirectory(currentServerDirectory);
    }

	/**
     * Disconnects from the FTP server.
     */
    public  void disconnect() throws IOException {
        try {
            sendLine("QUIT");
        }
        finally {
        	getDirectory.close();
            getDirectory = null;
            socket = null;
        }
    }
    
    /**
     * Returns the working directory of the FTP server it is connected to.
     */
    public String pwd() throws IOException {
        sendLine("PWD");
        String dir = null;
        String response = readLine();
        if (response.startsWith("257 ")) {
        	System.out.println("PWD "+ response);
            int fsLastIndex = response.lastIndexOf(System.getProperty("file.separator"));
            if (fsLastIndex > 0) 
                dir = response.substring(fsLastIndex + 1);
             else 
            	 dir = response.substring(4);
        }
        return dir;
    }
    
    /**
     * Get complete path to the working directory on the server
     */
    public String completePwd() throws IOException {
    	sendLine("CPWD");
    	response = readLine();
    	if(response.startsWith("257 ")) {
    		return response.substring(4);
    	}
    	return null;
    }
    
    /**
     * Sends a file to be stored on the FTP server.
     * Returns true if the file transfer was successful.
     * The file is sent in passive mode to avoid NAT or firewall problems
     * at the client end.
     */
    public boolean stor(File file) throws IOException {
        if (file.isDirectory()) {
            throw new IOException("SimpleFTP cannot upload a directory.");
        }
        
        pasv();
        
        sendLine("STOR " + file.getName());
        
        Socket dataSocket = new Socket(ip, port);
        
        response = readLine();
        if (!response.startsWith("150 ")) {
        	dataSocket.close();
            throw new IOException("SimpleFTP was not allowed to send the file: " + response);
        }
        
        sendFile(file, dataSocket);
        
        dataSocket.close();
        response = readLine();
        return response.startsWith("226 ");
    }
    
    /**
     * Retrieve files stored on the FTP server.
     * Returns true if all the file were transfered successfully.
     */
    public boolean retr(File directory, String filePath) throws IOException {
		// Ask for passive mode
		pasv();
		// Ask for the file
		sendLine("RETR " + filePath);
		
		Socket dataSocket = new Socket(ip, port);
		
        response = readLine();
        if (!response.startsWith("160 ")) {
        	dataSocket.close();
            throw new IOException("SimpleFTP was not allowed to receive the file: " + response);
        }
        if(!filePath.contains(", ")) {
        	dataSocket.close();
        	throw new IOException("Cannot download root directory.");
        }
        // Get name & path of the new file to save
        String fileName = filePath.substring(17).replace("]", "");
        int size = fileName.split(", ").length;
        if(size > 1) {
        	fileName = fileName.split(", ")[size - 1];
        }
        
        response = readLine();
        if(!response.startsWith("185 ")) {
        	dataSocket.close();
        	throw new IOException(response);
        }
        
        File toGet = new File(directory.getAbsolutePath()        												
        							+ System.getProperty("file.separator")
        							+ fileName);
        
        readFile(toGet, dataSocket);
        dataSocket.close();
		response = readLine();
		return response.startsWith("222 ");		
    }
    
    /**
     * Returns a list of files from a directory specified. 
     */
    public String ls(boolean useJson) throws IOException{
    	if(useJson)
    		sendLine("LIST json");
    	else
    		sendLine("LIST serialize");
    	
        String list = null;
        response = readLine();
        if (response.startsWith("200 ")) {
        	if (response.split(" ").length > 1)
        		list = response.split(" ")[1];
        	else
        		list = "No file listed in current directory.";
        }
        System.out.println("Got list from server : " + list);
        if(useJson) {
        	response = readLine();
        	if (response.startsWith("JSON ")) {
        		Gson gson = new Gson();
        		String json = response.substring(5);
        		FileTreeNode<FileData> root = gson.fromJson(json, FileTreeNode.class);
        		directoryTree = new DefaultMutableTreeNode(root.getData().getName());
        	    createNodes(directoryTree, root);
        	}
        	else {
        		throw new IOException("Failed to receive JSON string");
        	}
        }
        else {
        	try {
				directoryTree = (DefaultMutableTreeNode) getDirectory.readObject();
			} catch (ClassNotFoundException e) {
				throw new IOException("Cannot read tree from server : ClassNotFound.");
			} catch (StreamCorruptedException e) {
				throw new IOException("Cannot read tree from server : StreamCorrupted.");
			} catch (InvalidClassException e) {
				throw new IOException("Cannot read tree from server : InvalidClass.");
			} catch (OptionalDataException e) {
				throw new IOException("Cannot read tree from server : OptionalData.");
			}
        	
        }
        return list;
    }
    
    /**
     * Recursively create a tree from the FileTreeNode
     * @param tree the root of tree to complete
     * @param fileTreeNode the list of nodes to add to the tree
     */
    private static void createNodes(DefaultMutableTreeNode tree, FileTreeNode<FileData> fileTreeNode) {
        if (fileTreeNode.hasChildren()) {
          List<FileTreeNode<FileData>> fileTreeNodes = fileTreeNode.getChildren();

          for (FileTreeNode<FileData> child : fileTreeNodes) {
            DefaultMutableTreeNode subtree = new DefaultMutableTreeNode(child.getData().getName());
            createNodes(subtree, child);
            tree.add(subtree);
          }
        }
    }

    /**
     * Changes the working directory (like cd). Returns true if successful.
     */   
    public boolean cwd(String dir) throws IOException {
        sendLine("CWD " + dir);
        response = readLine();
        return (response.startsWith("250 "));
    }
    
    /**
     * Change working directory directly when the connection is made
     */
    private boolean changeStartDirectory(String currentServerDir) throws IOException {
    	sendLine("CSD " + currentServerDir);
    	response = readLine();
    	return (response.startsWith("260 "));
	}
    
    /**
     *  Request FTP server to be in passive mode
     */
    protected void pasv() throws IOException {
    	sendLine("PASV");
        String response = readLine();
        if (!response.startsWith("227 ")) {
            throw new IOException("SimpleFTP could not request passive mode: " + response);
        }
        
        ip = null;
        port = -1;
        int opening = response.indexOf('(');
        int closing = response.indexOf(')', opening + 1);
        if (closing > 0) {
            String dataLink = response.substring(opening + 1, closing);
            StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
            try {
                ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken();
                port = Integer.parseInt(tokenizer.nextToken()) * 256 + Integer.parseInt(tokenizer.nextToken());
            }
            catch (Exception e) {
                throw new IOException("SimpleFTP received bad data link information: " + response);
            }
        }
    }


    /**
     * Enter binary mode for sending binary files.
     */
    public boolean bin() throws IOException {
        sendLine("TYPE I");
        String response = readLine();
        return (response.startsWith("201 "));
    }
    
    
    /**
     * Enter ASCII mode for sending text files. This is usually the default
     * mode. Make sure you use binary mode if you are sending images or
     * other binary data, as ASCII mode is likely to corrupt them.
     */
    public boolean ascii() throws IOException {
        sendLine("TYPE A");
        String response = readLine();
        return (response.startsWith("202 "));
    }
    
    public DefaultMutableTreeNode getDirectoryTree() {
    	return this.directoryTree;
    }

}